/*!	 filesystem.h
**	 FileSystem
**
**	......... ... 2013 Ivan Mahonin
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifndef __SYNFIG_FILESYSTEM_H
#define __SYNFIG_FILESYSTEM_H

#include <cstdio>

#include <istream>
#include <ostream>
#include <streambuf>
#include <vector>

#include <ETL/handle>

#include "string.h"

namespace synfig
{

class FileSystem : public etl::shared_object
{
public:
    typedef etl::handle<FileSystem> Handle;
    typedef std::vector<String> FileList;

    class Stream : public etl::shared_object
    {
    public:
        typedef etl::handle<Stream> Handle;

    protected:
        FileSystem::Handle file_system_;
        Stream(FileSystem::Handle file_system);
    public:
        virtual ~Stream();
        FileSystem::Handle file_system() const
        {
            return file_system_;
        }
    };

    class ReadStream :
        public Stream,
        private std::streambuf,
        public std::istream
    {
    public:
        typedef etl::handle<ReadStream> Handle;

    protected:
        char buffer_;

        ReadStream(FileSystem::Handle file_system);
        virtual int underflow();
        virtual size_t internal_read(void *buffer, size_t size) = 0;

    public:
        size_t read_block(void *buffer, size_t size)
        {
            return read((char*)buffer, size).gcount();
        }
        bool read_whole_block(void *buffer, size_t size)
        {
            return size == read_block(buffer, size);
        }
        template<typename T> bool read_variable(T &v)
        {
            return read_whole_block(&v, sizeof(T));
        }
    };

    class WriteStream :
        public Stream,
        private std::streambuf,
        public std::ostream
    {
    public:
        typedef etl::handle<WriteStream> Handle;

    protected:
        WriteStream(FileSystem::Handle file_system);
        virtual int overflow(int ch);
        virtual size_t internal_write(const void *buffer, size_t size) = 0;

    public:
        bool write_block(const void *buffer, size_t size)
        {
            for (size_t i = 0; i < size; i++)
                if (!put(((const char*)buffer)[i]).good()) {
                    return i;
                }

            return size;
        }
        bool write_whole_block(const void *buffer, size_t size)
        {
            return size == write_block(buffer, size);
        }
        bool write_whole_stream(std::streambuf &streambuf)
        {
            return (*this << &streambuf).good();
        }
        bool write_whole_stream(std::istream &stream)
        {
            return write_whole_stream(*stream.rdbuf());
        }
        bool write_whole_stream(ReadStream::Handle stream)
        {
            return !stream || write_whole_stream(*(std::istream*) & (*stream));
        }
        template<typename T> bool write_variable(const T &v)
        {
            return write_whole_block(&v, sizeof(T));
        }
    };

    class Identifier
    {
    public:
        FileSystem::Handle file_system;
        String filename;
        Identifier() { }
        Identifier(const FileSystem::Handle &file_system, const String &filename):
            file_system(file_system), filename(filename) { }

        bool empty() const
        {
            return file_system;
        }
        operator bool () const
        {
            return !empty();
        }

        bool operator < (const Identifier &other) const
        {
            if (file_system.get() < other.file_system.get()) {
                return true;
            }

            if (other.file_system.get() < file_system.get()) {
                return false;
            }

            if (filename < other.filename) {
                return true;
            }

            if (other.filename < filename) {
                return false;
            }

            return false;
        }
        bool operator > (const Identifier &other) const
        {
            return other < *this;
        }
        bool operator != (const Identifier &other) const
        {
            return *this > other || other < *this;
        }
        bool operator == (const Identifier &other) const
        {
            return !(*this != other);
        }

        ReadStream::Handle get_read_stream() const;
        WriteStream::Handle get_write_stream() const;
    };

    FileSystem();
    virtual ~FileSystem();

    virtual bool is_file(const String &filename) = 0;
    virtual bool is_directory(const String &filename) = 0;

    virtual bool directory_create(const String &dirname) = 0;
    virtual bool directory_scan(const String &dirname, FileList &out_files) = 0;

    virtual bool file_remove(const String &filename) = 0;
    virtual bool file_rename(const String &from_filename, const String &to_filename);
    virtual ReadStream::Handle get_read_stream(const String &filename) = 0;
    virtual WriteStream::Handle get_write_stream(const String &filename) = 0;
    virtual String get_real_uri(const String &filename);

    inline bool is_exists(const String filename)
    {
        return is_file(filename) || is_directory(filename);
    }

    String get_real_filename(const String &filename);
    Identifier get_identifier(const String &filename)
    {
        return Identifier(this, filename);
    }

    bool directory_create_recursive(const String &dirname);
    bool remove_recursive(const String &filename);

    static bool copy(Handle from_file_system, const String &from_filename, Handle to_file_system, const String &to_filename);
    static bool copy_recursive(Handle from_file_system, const String &from_filename, Handle to_file_system, const String &to_filename);

    static String fix_slashes(const String &filename);

    // /@brief Read a stream line by line even '\r\n' ended
    static std::istream& safe_get_line(std::istream& is, String& t);
};

// Always empty filesystem (dummy)
class FileSystemEmpty : public FileSystem
{
public:
    typedef etl::handle<FileSystemEmpty> Handle;

    FileSystemEmpty();
    virtual ~FileSystemEmpty();

    virtual bool is_file(const String & /*filename*/)
    {
        return false;
    }
    virtual bool is_directory(const String &filename)
    {
        return fix_slashes(filename).empty();
    }

    virtual bool directory_create(const String &dirname)
    {
        return is_directory(dirname);
    }
    virtual bool directory_scan(const String &dirname, FileList &out_files)
    {
        out_files.clear();
        return is_directory(dirname);
    }

    virtual bool file_remove(const String &filename)
    {
        return !is_directory(filename);
    }
    virtual bool file_rename(const String &from_filename, const String &to_filename)
    {
        return is_directory(from_filename) && is_directory(to_filename);
    }
    virtual FileSystem::ReadStream::Handle get_read_stream(const String &/*filename*/)
    {
        return ReadStream::Handle();
    }
    virtual FileSystem::WriteStream::Handle get_write_stream(const String &/*filename*/)
    {
        return WriteStream::Handle();
    }
};
}

#endif